#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <time.h>
#include <string.h>

#define IS_ANIMATED	(0)

#ifndef NULL
#define NULL ((void *)0)
#endif

typedef struct
{
	float x;
	float y;
} vec2_t;

typedef struct
{
	float time_total;
} shaderargs_t;

typedef struct
{
	char *name;
	float (*shade)(vec2_t *, shaderargs_t *);
} style_t;

float uv_clamp(float x)
{
	if (x < 0.0f)
		x = 0.0f;
	else if (x > 1.0f)
		x = 1.0f;

	return x;
}

char * float2color(float x)
{
	x = uv_clamp(x);
	char *color = malloc(sizeof(char) * 16);
	#if 0
	int min = 31;	
	int max = 37;
	int c = (int)(x * (float)(max - min)) + min;
	// c = 31;	// red
	// c = 32;	// green
	// c = 33;	// yellow
	// c = 34;	// blue
	// c = 35;	// violet
	// c = 36;	// teal
	// c = 37;	// white
	#endif
	const int rainbow_size = 7;
	int rainbow[] = { 31, 33, 32, 36, 34, 35, 37 };
	int c = rainbow[(int)(x * (float)rainbow_size)];	
	sprintf(color, "\x1B[%dm", c);
	return color;
	//return "\x1B[37m";
}

char float2char(float x)
{
	x = uv_clamp(x);
	return (char)(x * ('9' - '0' + 1)) + '0';
}

float uv_lerp(float x, float y, float pct)
{
	float ret = x + (y - x) * pct;
	return ret;
}

float shade_linear(vec2_t *uv, shaderargs_t *args)
{
	return uv->x;
}

float shade_rad(vec2_t *uv, shaderargs_t *args)
{
	vec2_t center, delta;
	float rad;

	center.x = 0.5f;
	center.y = 0.5f;
	delta.x = uv->x - center.x;
	delta.y = uv->y - center.y;
	rad = sqrtf(delta.x * delta.x + delta.y * delta.y);
	return rad;
}

float shade_rad_anim(vec2_t *uv, shaderargs_t *args)
{
	vec2_t center, delta;
	float rad, sin_lerp;

	center.x = 0.5f;
	center.y = 0.5f;
	delta.x = uv->x - center.x;
	delta.y = uv->y - center.y;
	rad = sqrtf(delta.x * delta.x + delta.y * delta.y);
	sin_lerp = 0.5f * (1.0f + sinf(args->time_total * 6.28f * 0.1f));
	rad *= sin_lerp;
	return rad;
}

float shade_sin(vec2_t *uv, shaderargs_t *args)
{
	return 0.5f * (1.0f + sinf(uv->x * 6.28f * 3));
}

float shade_sincos(vec2_t *uv, shaderargs_t *args)
{
	return 0.5f * (1.0f + sinf(uv->x * 6.28f * 3)
			*cosf(uv->y * 6.28f * 5));
}

float shade_sincos_anim(vec2_t *uv, shaderargs_t *args)
{
	float ret, sin_lerp;
	ret = 0.5f * (1.0f + sinf(uv->x * 6.28f * 3)
			*cosf(uv->y * 6.28f * 5));
	sin_lerp = 0.5f * (1.0f + sinf(args->time_total * 0.5f));
	ret *= sin_lerp;
	return ret;
}

float shade_rand(vec2_t *uv, shaderargs_t *args)
{
	float ret;

	ret = (float)rand() / (float)RAND_MAX;
	return ret;
}

float shade_ocean(vec2_t *uv, shaderargs_t *args)
{
	float ret_x, ret_y, ret;
	ret_x = sinf(uv->x * 6.28 * 3.0f);
	ret_y = cosf(uv->y * 6.28 * 3.0f);
	ret = uv_lerp(ret_x, ret_y, 0.5f);
	return ret;
}

float shade_cosxy(vec2_t *uv, shaderargs_t *args)
{
	float ret;
	const float amp = 4.0f;
	const float x_min = -amp;
	const float x_max = amp;
	const float y_min = -amp;
	const float y_max = amp;
	float x = uv_lerp(x_min, x_max, uv->x);
	float y = uv_lerp(y_min, y_max, uv->y);
	ret = 0.5f * (1.0f + cosf(x * y));
	return ret;
}

float shade_cosxy_anim(vec2_t *uv, shaderargs_t *args)
{
	float ret;
	const float amp_min = 1.0f;
	const float amp_max = 16.0f;
	float sinetime = 0.5f * (1.0f + sinf(args->time_total * 6.28*0.1*0.9));
	float amp = uv_lerp(amp_min, amp_max, sinetime);
	const float x_min = -amp;
	const float x_max = amp;
	const float y_min = -amp;
	const float y_max = amp;
	float x = uv_lerp(x_min, x_max, uv->x);
	float y = uv_lerp(y_min, y_max, uv->y);
	ret = 0.5f * (1.0f + cosf(x * y));
	return ret;
}

float shade_ninja(vec2_t *uv, shaderargs_t *args)
{
	const float empty = 0.0f;
	const float solid = 0.99f;
	float ret = 0.0f;
	const float t = 0.1f; // // 0.1f;	
	const float ht = t * 0.5f;
	char *text = " C O O N S ";
	// char *text = " T T T ";
	// char *text = " E E ";
	int k;
	int len=strlen(text);
	float pct_per_letter = 1.0f / (float)len;
	// for a given (u,v) find corresponding letter:
	for (k=0; k<len; k++)
	{
		float pct_k = (float)k * pct_per_letter;
		float pct_k1 = (float)(k+1) * pct_per_letter;
		if (pct_k <= uv->x && uv->x <= pct_k1)
		{
			vec2_t uv_let, center;
			uv_let.x = (uv->x - pct_k) / pct_per_letter;
			uv_let.y = uv->y;
			center.x = 0.5f;
			center.y = 0.5f;
			float x2c = fabs(uv_let.x - center.x);
			float y2c = fabs(uv_let.y - center.y);
			ret = empty;
			switch (text[k])
			{
			case ' ':
				break;
			case 'O':
				if (uv_let.y <= t ||
				    uv_let.y >= 1.0f - t ||
				    uv_let.x <= t ||
				    uv_let.x >= 1.0f - t) ret = solid;
				break;
			case 'C':
				if (uv_let.y <= t ||
				    uv_let.y >= 1.0f - t ||
				    uv_let.x <= t) ret = solid;
				break;
			case 'I':
				if (center.x - t <= uv_let.x &&
				    uv_let.x <= center.x + t) ret = solid;
				break;
			case 'T':
				if (uv_let.y <= t ||
				    x2c <= t) ret = solid;
				break;
			case 'E':
				if (uv_let.y <= t ||
				    uv_let.y >= 1.0f - t ||
				    uv_let.x <= t ||
				    y2c <= t) ret = solid;
				break;
			case 'N':
				if (uv_let.x <= t ||
				    uv_let.x >= 1.0f - t ||
				    (uv_let.x - t <= uv_let.y &&
				     uv_let.y <= uv_let.x + t)) ret = solid;
				break;
			case 'S':
				if (uv_let.y <= t ||
				    uv_let.y >= 1.0f - t ||
				    (uv_let.x <= t && uv_let.y <= center.y) ||
				    (uv_let.x >= 1.0f - t
					&& uv_let.y >= center.y) ||
				    y2c <= t) ret = solid;
				break;
			}

			// ret = pct_k;
			// return ret;
		}
	}

	return ret;
}

style_t styles[] =
{
	{ "linear",		shade_linear		},
	{ "rad",		shade_rad		},
	{ "rad_anim",		shade_rad_anim		},
	{ "sin",		shade_sin		},
	{ "sincos",		shade_sincos		},
	{ "sincos_anim",	shade_sincos_anim	},
	{ "rand",		shade_rand		},
	{ "ocean",		shade_ocean		},
	{ "cosxy",		shade_cosxy		},
	{ "cosxy_anim",		shade_cosxy_anim	},
	{ "ninja",		shade_ninja		}
};

void cls()
{
	system("clear");
}

void print_usage_and_exit()
{
	int k;
	int nr_styles = sizeof(styles)/sizeof(style_t);
	printf("Usage: mesh <sizex> <sizey> <style>\n");
	printf("\n");
	printf("    where...\n");
	printf("        <sizex> = mesh width\n");
	printf("	<sizey> = mesh height\n");
	printf("	<style> =\n");
	for (k=0; k<nr_styles; k++)
		printf("		%s%s\n",
		       styles[k].name, (k == nr_styles-1) ? "" : " |");

	exit(-1);
}

void display_mesh(int sizex, int sizey, shaderargs_t *args, style_t *style)
{
	int x, y;
	float shadef;
	char shadec, *color;
	vec2_t uv;
	for (y=0; y<sizey; y++)
	{
		for (x=0; x<sizex; x++)
		{
			uv.x = (float)x / (float)sizex;
			uv.y = (float)y / (float)sizey;
			shadef = style->shade(&uv, args);
			shadef = uv_clamp(shadef);
			shadec = float2char(shadef);
			color = float2color(shadef);
			printf("%s%c", color, shadec);
		}

		printf("\n");
	}

	printf("\r");
}

style_t * get_style_by_name(char *style_name)
{
	int k;
	int nr_styles = sizeof(styles)/sizeof(style_t);
	for (k=0; k<nr_styles; k++)
		if (0 == strcmp(style_name, styles[k].name))
			return &styles[k];

	return NULL;
}

int main(int argc, char *argv[])
{
	volatile time_t t_start, t_end;
	volatile double elapsed;
	shaderargs_t shaderargs;
	int sizex, sizey;
	char *style_name;
	style_t *style;
	if (argc != 4)
		print_usage_and_exit();	

	sizex = atoi(argv[1]);
	sizey = atoi(argv[2]);
	style_name = argv[3];
	if (!(style = get_style_by_name(style_name)))
		print_usage_and_exit();

	t_start = time(NULL);
	for (;;)
	{
		if (IS_ANIMATED) cls();
		t_end = time(NULL);
		elapsed = difftime(t_end, t_start);
		shaderargs.time_total = (float)elapsed;
		//printf("Start: 0x%08x ... End: 0x%08x ... Elapsed: 0x%08x (%f, %d)\n", (void *)t_start, (void *)t_end, elapsed, (float)elapsed, (int)elapsed);
		display_mesh(sizex, sizey, &shaderargs, style);
		if (!IS_ANIMATED) break;
	}

	return 0;
}


